﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using NHibernate;
using AsyncPersistence.Entities;
using NHibernate.Cfg;

namespace AsyncPersistence.Services
{
    public class PersitenceService : IPersistenceService
    {
        #region Fields
        private ISessionFactory sessionFactory;
        private ISession session;
        #endregion

        #region Constructor
        public PersitenceService()
        {
            Configuration configuration = new Configuration();
            configuration.Configure();
            configuration.AddAssembly(typeof(Client).Assembly);
            sessionFactory = configuration.BuildSessionFactory();
            sessionFactory.OpenSession();

            session = AcquireSession();
        }
        #endregion

        #region Public members
        public bool Save(PersistableEntity persistableEntity)
        {
            return Save(new List<PersistableEntity>() { persistableEntity });
        }

        public bool Save(IEnumerable<PersistableEntity> persistableEntities)
        {
            lock (this)
            {
                ITransaction trans;
                try
                {
                    trans = session.BeginTransaction();
                    foreach (var persistableEntity in persistableEntities)
                        session.SaveOrUpdate(persistableEntity);
                    trans.Commit();
                    session.Flush();
                    return true;
                }
                catch (Exception ex)
                {
                    session.Close();
                    session = AcquireSession();
                    return false;
                }
            }
        }

        public bool Delete(PersistableEntity persistableEntity)
        {
            return Delete(new List<PersistableEntity>() { persistableEntity });
        }

        public bool Delete(IEnumerable<PersistableEntity> persistableEntities)
        {
            lock (this)
            {
                ITransaction trans;
                try
                {
                    trans = session.BeginTransaction();
                    foreach (var persistableEntity in persistableEntities)
                        session.Delete(persistableEntity);
                    trans.Commit();
                    session.Flush();
                    return true;
                }
                catch (Exception ex)
                {
                    session.Close();
                    session = AcquireSession();
                    return false;
                }
            }
        }

        public IList<T> FetchListByQueryOver<T>(bool forceUpdate, Expression<Func<T, bool>> whereExpression) where T : PersistableEntity
        {
            lock (this)
            {
                try
                {
                    IList<T> result;

                    if (forceUpdate)
                    {
                        //if client forces the update, then we need to re-read the database
                        ISession querySession = AcquireSession();
                        IList<T> t1 = ExecuteRequest(querySession, whereExpression);
                        querySession.Close();

                        result = new List<T>(t1.Count);

                        foreach (var item in t1)
                        {
                            var sessionItem = session.Get<T>(item.Id);
                            if (item.Equals(sessionItem) == false)
                            {
                                session.Refresh(sessionItem);
                            }

                            result.Add(sessionItem);
                        }
                    }
                    else
                        result = ExecuteRequest(session, whereExpression);
                    return result;
                }
                catch (Exception ex)
                {
                    session.Close();
                    session = AcquireSession();
                }
                return new List<T>();
            }
        }

        public void Dispose()
        {
            if (sessionFactory != null)
            {
                sessionFactory.Close();
                sessionFactory.Dispose();
                sessionFactory = null;
            }
        }
        #endregion

        #region Private members
        private ISession AcquireSession()
        {
            if (sessionFactory == null)
            {
                throw new HibernateException("Session factory not initialized");
            }
            try
            {
                ISession acquireSession = sessionFactory.OpenSession();
                acquireSession.FlushMode = FlushMode.Never;
                return acquireSession;
            }
            catch (Exception ex)
            {
                throw new HibernateException("Could not open session", ex);
            }
        }

        private static IList<T> ExecuteRequest<T>(ISession querySession, Expression<Func<T, bool>> whereExpression) where T : PersistableEntity
        {
            ITransaction trans = querySession.BeginTransaction();
            IQueryOver<T, T> queryOver = whereExpression != null ? querySession.QueryOver<T>().Where(whereExpression) : querySession.QueryOver<T>();

            var tl = queryOver.List();
            trans.Commit();
            return tl;
        }
        #endregion
    }
}
